"use strict";

exports.__esModule = true;
require("core-js/modules/es.error.cause.js");
var _object = require("../../helpers/object");
var _localHooks = _interopRequireDefault(require("../../mixins/localHooks"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
/**
 * The BaseTransformation class implements algorithms for transforming coordinates based on current settings
 * passed to the Handsontable. The class performs the calculations based on the renderable indexes.
 *
 * Transformation is always applied relative to the currently active selection layer.
 *
 * The class operates on a table size defined by the renderable indexes. If the `navigableHeaders`
 * option is enabled, the table size is increased by the number of row and/or column headers.
 * Because the headers are treated as cells as part of the table size (indexes always go from 0 to N),
 * the algorithm can be written as simply as possible (without new if's that distinguish the headers
 * logic).
 *
 * @class BaseTransformation
 * @private
 */
var _range = /*#__PURE__*/new WeakMap();
var _activeLayerIndex = /*#__PURE__*/new WeakMap();
var _offset = /*#__PURE__*/new WeakMap();
var _BaseTransformation_brand = /*#__PURE__*/new WeakSet();
class BaseTransformation {
  constructor(range, tableApi) {
    /**
     * Chooses the next selection layer as active one.
     */
    _classPrivateMethodInitSpec(this, _BaseTransformation_brand);
    /**
     * Instance of the SelectionRange, holder for visual coordinates applied to the table.
     *
     * @type {SelectionRange}
     */
    _classPrivateFieldInitSpec(this, _range, void 0);
    /**
     * Index of the currently active selection layer.
     *
     * @type {number}
     */
    _classPrivateFieldInitSpec(this, _activeLayerIndex, 0);
    /**
     * Increases the table size by applying the offsets. The option is used by the `navigableHeaders`
     * option.
     *
     * @type {{ x: number, y: number }}
     */
    _classPrivateFieldInitSpec(this, _offset, {
      x: 0,
      y: 0
    });
    /**
     * An object containing the table API methods and settings.
     *
     * @type {object}
     */
    _defineProperty(this, "tableApi", void 0);
    _classPrivateFieldSet(_range, this, range);
    this.tableApi = tableApi;
  }

  /**
   * Sets the currently active selection layer index.
   *
   * @param {number} layerIndex The layer index to set as active.
   */
  setActiveLayerIndex(layerIndex) {
    _classPrivateFieldSet(_activeLayerIndex, this, layerIndex);
  }

  /**
   * Gets the currently active selection layer range.
   *
   * @returns {CellRange}
   */
  getCurrentSelection() {
    return _classPrivateFieldGet(_range, this).peekByIndex(_classPrivateFieldGet(_activeLayerIndex, this));
  }

  /**
   * Selects cell relative to the current cell (if possible).
   *
   * @param {number} rowDelta Rows number to move, value can be passed as negative number.
   * @param {number} colDelta Columns number to move, value can be passed as negative number.
   * @param {boolean} [createMissingRecords=false] If `true` the new rows/columns will be created if necessary. Otherwise, row/column will
   *                        be created according to `minSpareRows/minSpareCols` settings of Handsontable.
   * @returns {{selectionLayer: number, visualCoords: CellCoords}} Visual coordinates with selection layer after transformation.
   */
  transformStart(rowDelta, colDelta) {
    let createMissingRecords = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
    _classPrivateFieldSet(_offset, this, this.calculateOffset());
    const delta = this.tableApi.createCellCoords(rowDelta, colDelta);
    let visualCoords = this.getCurrentSelection().highlight;
    const highlightRenderableCoords = this.tableApi.visualToRenderableCoords(visualCoords);
    let rowTransformDir = 0;
    let colTransformDir = 0;
    this.runLocalHooks('beforeTransformStart', delta);
    if (highlightRenderableCoords.row !== null && highlightRenderableCoords.col !== null) {
      let {
        width,
        height
      } = _assertClassBrand(_BaseTransformation_brand, this, _getTableSize).call(this);
      const {
        row,
        col
      } = _assertClassBrand(_BaseTransformation_brand, this, _visualToZeroBasedCoords).call(this, visualCoords);
      const fixedRowsBottom = this.tableApi.fixedRowsBottom();
      const minSpareRows = this.tableApi.minSpareRows();
      const minSpareCols = this.tableApi.minSpareCols();
      const autoWrapRow = this.tableApi.autoWrapRow();
      const autoWrapCol = this.tableApi.autoWrapCol();
      const zeroBasedCoords = this.tableApi.createCellCoords(row + delta.row, col + delta.col);
      if (zeroBasedCoords.row >= height) {
        const isActionInterrupted = (0, _object.createObjectPropListener)(createMissingRecords && minSpareRows > 0 && fixedRowsBottom === 0);
        const nextColumn = zeroBasedCoords.col + 1;
        const isColumnOutOfRange = nextColumn >= width;
        const newCoords = this.tableApi.createCellCoords(zeroBasedCoords.row - height, isColumnOutOfRange ? nextColumn - width : nextColumn);
        this.runLocalHooks('beforeColumnWrap', isActionInterrupted, _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, newCoords), isColumnOutOfRange);
        if (isActionInterrupted.value) {
          this.runLocalHooks('insertRowRequire', this.countRenderableRows());
        } else if (autoWrapCol) {
          if (this.shouldSwitchSelectionLayer() && isColumnOutOfRange && _classPrivateFieldGet(_range, this).size() > 1) {
            _assertClassBrand(_BaseTransformation_brand, this, _chooseNextSelectionLayer).call(this);
            const nextLayerCoords = _assertClassBrand(_BaseTransformation_brand, this, _findNonHiddenZeroBasedCoordsInSelection).call(this, 'forward');
            if (nextLayerCoords !== null) {
              newCoords.assign(nextLayerCoords);
            }
          }
          zeroBasedCoords.assign(newCoords);
        }
      } else if (zeroBasedCoords.row < 0) {
        const isActionInterrupted = (0, _object.createObjectPropListener)(autoWrapCol);
        const previousColumn = zeroBasedCoords.col - 1;
        const isColumnOutOfRange = previousColumn < 0;
        const newCoords = this.tableApi.createCellCoords(height + zeroBasedCoords.row, isColumnOutOfRange ? width + previousColumn : previousColumn);
        this.runLocalHooks('beforeColumnWrap', isActionInterrupted, _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, newCoords), isColumnOutOfRange);
        if (autoWrapCol) {
          if (this.shouldSwitchSelectionLayer() && isColumnOutOfRange && _classPrivateFieldGet(_range, this).size() > 1) {
            _assertClassBrand(_BaseTransformation_brand, this, _choosePreviousSelectionLayer).call(this);
            const nextLayerCoords = _assertClassBrand(_BaseTransformation_brand, this, _findNonHiddenZeroBasedCoordsInSelection).call(this, 'backward');
            if (nextLayerCoords !== null) {
              newCoords.assign(nextLayerCoords);
            }
          }
          zeroBasedCoords.assign(newCoords);
        }
      }

      // the range size may be changed after the column wrap, so we need to recalculate it for row wrap
      ({
        width,
        height
      } = _assertClassBrand(_BaseTransformation_brand, this, _getTableSize).call(this));
      if (zeroBasedCoords.col >= width) {
        const isActionInterrupted = (0, _object.createObjectPropListener)(createMissingRecords && minSpareCols > 0);
        const nextRow = zeroBasedCoords.row + 1;
        const isRowOutOfRange = nextRow >= height;
        const newCoords = this.tableApi.createCellCoords(isRowOutOfRange ? nextRow - height : nextRow, zeroBasedCoords.col - width);
        this.runLocalHooks('beforeRowWrap', isActionInterrupted, _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, newCoords), isRowOutOfRange);
        if (isActionInterrupted.value) {
          this.runLocalHooks('insertColRequire', this.countRenderableColumns());
        } else if (autoWrapRow) {
          if (this.shouldSwitchSelectionLayer() && isRowOutOfRange && _classPrivateFieldGet(_range, this).size() > 1) {
            _assertClassBrand(_BaseTransformation_brand, this, _chooseNextSelectionLayer).call(this);
            const nextLayerCoords = _assertClassBrand(_BaseTransformation_brand, this, _findNonHiddenZeroBasedCoordsInSelection).call(this, 'forward');
            if (nextLayerCoords !== null) {
              newCoords.assign(nextLayerCoords);
            }
          }
          zeroBasedCoords.assign(newCoords);
        }
      } else if (zeroBasedCoords.col < 0) {
        const isActionInterrupted = (0, _object.createObjectPropListener)(autoWrapRow);
        const previousRow = zeroBasedCoords.row - 1;
        const isRowOutOfRange = previousRow < 0;
        const newCoords = this.tableApi.createCellCoords(isRowOutOfRange ? height + previousRow : previousRow, width + zeroBasedCoords.col);
        this.runLocalHooks('beforeRowWrap', isActionInterrupted, _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, newCoords), isRowOutOfRange);
        if (autoWrapRow) {
          if (this.shouldSwitchSelectionLayer() && isRowOutOfRange && _classPrivateFieldGet(_range, this).size() > 1) {
            _assertClassBrand(_BaseTransformation_brand, this, _choosePreviousSelectionLayer).call(this);
            const nextLayerCoords = _assertClassBrand(_BaseTransformation_brand, this, _findNonHiddenZeroBasedCoordsInSelection).call(this, 'backward');
            if (nextLayerCoords !== null) {
              newCoords.assign(nextLayerCoords);
            }
          }
          zeroBasedCoords.assign(newCoords);
        }
      }
      const {
        rowDir,
        colDir
      } = _assertClassBrand(_BaseTransformation_brand, this, _clampCoords).call(this, zeroBasedCoords);
      rowTransformDir = rowDir;
      colTransformDir = colDir;
      visualCoords = _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, zeroBasedCoords);
    }
    this.runLocalHooks('afterTransformStart', visualCoords, rowTransformDir, colTransformDir);
    return {
      selectionLayer: _classPrivateFieldGet(_activeLayerIndex, this),
      visualCoords
    };
  }

  /**
   * Sets selection end cell relative to the current selection end cell (if possible).
   *
   * @param {number} rowDelta Rows number to move, value can be passed as negative number.
   * @param {number} colDelta Columns number to move, value can be passed as negative number.
   * @returns {{selectionLayer: number, visualCoords: CellCoords}} Visual coordinates with selection layer after transformation.
   */
  transformEnd(rowDelta, colDelta) {
    _classPrivateFieldSet(_offset, this, this.calculateOffset());
    const delta = this.tableApi.createCellCoords(rowDelta, colDelta);
    const cellRange = this.getCurrentSelection();
    const highlightRenderableCoords = this.tableApi.visualToRenderableCoords(cellRange.highlight);
    const toRow = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedRow).call(this, cellRange.to.row, cellRange.from.row);
    const toColumn = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedColumn).call(this, cellRange.to.col, cellRange.from.col);
    const visualCoords = cellRange.to.clone();
    let rowTransformDir = 0;
    let colTransformDir = 0;
    this.runLocalHooks('beforeTransformEnd', delta);
    if (highlightRenderableCoords.row !== null && highlightRenderableCoords.col !== null && toRow !== null && toColumn !== null) {
      const {
        row: highlightRow,
        col: highlightColumn
      } = _assertClassBrand(_BaseTransformation_brand, this, _visualToZeroBasedCoords).call(this, cellRange.highlight);
      const coords = this.tableApi.createCellCoords(toRow + delta.row, toColumn + delta.col);
      const topStartCorner = cellRange.getTopStartCorner();
      const topEndCorner = cellRange.getTopEndCorner();
      const bottomEndCorner = cellRange.getBottomEndCorner();
      if (delta.col < 0 && toColumn >= highlightColumn && coords.col < highlightColumn) {
        const columnRestDelta = coords.col - highlightColumn;
        coords.col = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedColumn).call(this, topStartCorner.col, topEndCorner.col) + columnRestDelta;
      } else if (delta.col > 0 && toColumn <= highlightColumn && coords.col > highlightColumn) {
        const endColumnIndex = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedColumn).call(this, topEndCorner.col, topStartCorner.col);
        const columnRestDelta = Math.max(coords.col - endColumnIndex, 1);
        coords.col = endColumnIndex + columnRestDelta;
      }
      if (delta.row < 0 && toRow >= highlightRow && coords.row < highlightRow) {
        const rowRestDelta = coords.row - highlightRow;
        coords.row = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedRow).call(this, topStartCorner.row, bottomEndCorner.row) + rowRestDelta;
      } else if (delta.row > 0 && toRow <= highlightRow && coords.row > highlightRow) {
        const bottomRowIndex = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedRow).call(this, bottomEndCorner.row, topStartCorner.row);
        const rowRestDelta = Math.max(coords.row - bottomRowIndex, 1);
        coords.row = bottomRowIndex + rowRestDelta;
      }
      const {
        rowDir,
        colDir
      } = _assertClassBrand(_BaseTransformation_brand, this, _clampCoords).call(this, coords);
      rowTransformDir = rowDir;
      colTransformDir = colDir;
      const newVisualCoords = _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, coords);
      if (delta.row === 0 && delta.col !== 0) {
        visualCoords.col = newVisualCoords.col;
      } else if (delta.row !== 0 && delta.col === 0) {
        visualCoords.row = newVisualCoords.row;
      } else {
        visualCoords.row = newVisualCoords.row;
        visualCoords.col = newVisualCoords.col;
      }
    }
    this.runLocalHooks('afterTransformEnd', visualCoords, rowTransformDir, colTransformDir);
    return {
      selectionLayer: _classPrivateFieldGet(_activeLayerIndex, this),
      visualCoords
    };
  }

  /**
   * Abstract method that child classes must implement to calculate offset coordinates based on
   * the current processed selection layer.
   */
  calculateOffset() {
    throw new Error('`calculateOffset` is not implemented');
  }

  /**
   * Abstract method that child classes must implement to provide the count of renderable rows
   * based on their specific transformation logic.
   */
  countRenderableRows() {
    throw new Error('`countRenderableRows` is not implemented');
  }

  /**
   * Abstract method that child classes must implement to provide the count of renderable columns
   * based on their specific transformation logic.
   */
  countRenderableColumns() {
    throw new Error('`countRenderableColumns` is not implemented');
  }

  /**
   * Determines whether selection layer switching should occur during transformation.
   * Child classes can override this method to control the behavior.
   */
  shouldSwitchSelectionLayer() {
    throw new Error('`shouldSwitchSelectionLayer` is not implemented');
  }
}
exports.BaseTransformation = BaseTransformation;
function _chooseNextSelectionLayer() {
  const layerIndex = _classPrivateFieldGet(_activeLayerIndex, this) + 1;
  this.setActiveLayerIndex(layerIndex >= _classPrivateFieldGet(_range, this).size() ? 0 : layerIndex);
  _classPrivateFieldSet(_offset, this, this.calculateOffset());
}
/**
 * Chooses the previous selection layer as active one.
 */
function _choosePreviousSelectionLayer() {
  const layerIndex = _classPrivateFieldGet(_activeLayerIndex, this) - 1;
  this.setActiveLayerIndex(layerIndex < 0 ? _classPrivateFieldGet(_range, this).size() - 1 : layerIndex);
  _classPrivateFieldSet(_offset, this, this.calculateOffset());
}
/**
 * Clamps the coords to make sure they points to the cell (or header) in the table range.
 *
 * @param {CellCoords} zeroBasedCoords The coords object to clamp.
 * @returns {{rowDir: 1|0|-1, colDir: 1|0|-1}}
 */
function _clampCoords(zeroBasedCoords) {
  const {
    width,
    height
  } = _assertClassBrand(_BaseTransformation_brand, this, _getTableSize).call(this);
  let rowDir = 0;
  let colDir = 0;
  if (zeroBasedCoords.row < 0) {
    rowDir = -1;
    zeroBasedCoords.row = 0;
  } else if (zeroBasedCoords.row > 0 && zeroBasedCoords.row >= height) {
    rowDir = 1;
    zeroBasedCoords.row = height - 1;
  }
  if (zeroBasedCoords.col < 0) {
    colDir = -1;
    zeroBasedCoords.col = 0;
  } else if (zeroBasedCoords.col > 0 && zeroBasedCoords.col >= width) {
    colDir = 1;
    zeroBasedCoords.col = width - 1;
  }
  return {
    rowDir,
    colDir
  };
}
/**
 * Gets the table size in number of rows with headers as "height" and number of columns with
 * headers as "width".
 *
 * @returns {{width: number, height: number}}
 */
function _getTableSize() {
  return {
    width: _classPrivateFieldGet(_offset, this).x + this.countRenderableColumns(),
    height: _classPrivateFieldGet(_offset, this).y + this.countRenderableRows()
  };
}
/**
 * Finds non-hidden zero-based coordinates in the current selection range.
 *
 * @param {'forward' | 'backward'} direction The direction to search within the selection.
 * @returns {CellCoords | null} Zero-based coordinates or null if not found.
 */
function _findNonHiddenZeroBasedCoordsInSelection(direction) {
  if (!['forward', 'backward'].includes(direction)) {
    return null;
  }
  const topStartCoords = this.getCurrentSelection().getTopStartCorner();
  const bottomEndCoords = this.getCurrentSelection().getBottomEndCorner();
  const [visualRowFrom, visualRowTo, visualColumnFrom, visualColumnTo] = direction === 'forward' ? [topStartCoords.row, bottomEndCoords.row, topStartCoords.col, bottomEndCoords.col] : [bottomEndCoords.row, topStartCoords.row, bottomEndCoords.col, topStartCoords.col];
  const zeroBasedRow = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedRow).call(this, visualRowFrom, visualRowTo);
  const zeroBasedCol = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedColumn).call(this, visualColumnFrom, visualColumnTo);
  if (zeroBasedRow === null || zeroBasedCol === null) {
    return null;
  }
  return this.tableApi.createCellCoords(zeroBasedRow, zeroBasedCol);
}
/**
 * Finds the first non-hidden zero-based row in the table range.
 *
 * @param {number} visualRowFrom The visual row from which the search should start.
 * @param {number} visualRowTo The visual row to which the search should end.
 * @returns {number | null}
 */
function _findFirstNonHiddenZeroBasedRow(visualRowFrom, visualRowTo) {
  const row = this.tableApi.findFirstNonHiddenRenderableRow(visualRowFrom, visualRowTo);
  if (row === null) {
    return null;
  }
  return _classPrivateFieldGet(_offset, this).y + row;
}
/**
 * Finds the first non-hidden zero-based column in the table range.
 *
 * @param {number} visualColumnFrom The visual column from which the search should start.
 * @param {number} visualColumnTo The visual column to which the search should end.
 * @returns {number | null}
 */
function _findFirstNonHiddenZeroBasedColumn(visualColumnFrom, visualColumnTo) {
  const column = this.tableApi.findFirstNonHiddenRenderableColumn(visualColumnFrom, visualColumnTo);
  if (column === null) {
    return null;
  }
  return _classPrivateFieldGet(_offset, this).x + column;
}
/**
 * Translates the visual coordinates to zero-based ones.
 *
 * @param {CellCoords} visualCoords The visual coords to process.
 * @returns {CellCoords}
 */
function _visualToZeroBasedCoords(visualCoords) {
  const {
    row,
    col
  } = this.tableApi.visualToRenderableCoords(visualCoords);
  if (row === null || col === null) {
    throw new Error('Renderable coords are not visible.');
  }
  return this.tableApi.createCellCoords(_classPrivateFieldGet(_offset, this).y + row, _classPrivateFieldGet(_offset, this).x + col);
}
/**
 * Translates the zero-based coordinates to visual ones.
 *
 * @param {CellCoords} zeroBasedCoords The coordinates to process.
 * @returns {CellCoords}
 */
function _zeroBasedToVisualCoords(zeroBasedCoords) {
  const coords = zeroBasedCoords.clone();
  coords.col = zeroBasedCoords.col - _classPrivateFieldGet(_offset, this).x;
  coords.row = zeroBasedCoords.row - _classPrivateFieldGet(_offset, this).y;
  return this.tableApi.renderableToVisualCoords(coords);
}
(0, _object.mixin)(BaseTransformation, _localHooks.default);